import re
import StringIO
import logging
import socket

from hashlib import md5
import time
import random

import wsgiref
import wsgiref.handlers
import wsgiref.headers
from wsgiref import simple_server

from traceback import format_exc
from httplib import responses as HTTP_STATUS_MAP

from BitTornado.__init__ import product_name

DEBUG = True

class Request(object):
    """Request holder"""
    def __init__(self, environ):
        #http://ip:port/dir/file?param=1&another=2
        self.environ = environ        
        self.host   = environ.get('HTTP_HOST', '')          # ip:port
        self.scheme = environ.get('wsgi.url_scheme', '')    # http
        self.path   = environ.get('PATH_INFO', '')          # /dir/file
        self.query  = environ.get('QUERY_STRING', '')       # param=1&another=2
        self.method = environ.get('REQUEST_METHOD', '')     # GET
        self.authorization = environ.get('HTTP_AUTHORIZATION')
        self.fullpath = environ.get('SCRIPT_NAME', '') + self.path

    def parameters(self):
        parameters = {}        
        for param in self.query.split('&'):
            try:
                key, value = param.split("=")
            except ValueError:
                continue # bad parameter
            if key in parameters:
                if isinstance(parameters[key], list):
                    parameters[key].append(value)
                else:
                    parameters[key] = [parameters[key], value]
            else:
                parameters[key] = value
        return parameters
        
class Response(object):
    """Response holder"""
    def __init__(self):
        self.out = StringIO.StringIO()
        self.set_status(200)
        self.headers = wsgiref.headers.Headers([])
        self.headers['Content-Type'] = "text/html; charset=utf-8"
        self.headers['Cache-Control'] = "no-cache"
        
    def set_status(self, code, message = None):
        if not message:
            message = HTTP_STATUS_MAP[code]
        self.status = (code, message)

    def clear(self):
        self.out.seek(0)
        self.out.truncate(0)

    def wsgi_write(self, start_response):
        body = self.out.getvalue()
        if isinstance(body, unicode):
            body = body.encode('utf-8')
        self.headers['Content-Length'] = str(len(body))
        start_response('%d %s' % self.status, self.headers.items())
        self.out.close()
        return body
                    
class RequestHandler(object):
    """Abstract Request Handler"""
    def initialize(self, request, response):
        self.request = request
        self.response = response
        
    def get(self, *args):
        """Handler method for GET requests."""
        self.error(405)

    def post(self, *args):
        """Handler method for POST requests."""
        self.error(405)

    def head(self, *args):
        """Handler method for HEAD requests."""
        self.error(405)

    def options(self, *args):
        """Handler method for OPTIONS requests."""
        self.error(405)

    def trace(self, *args):
        """Handler method for TRACE requests."""
        self.error(405)

    def error(self, code):
        """Clears the response output stream and sets the given HTTP error code."""
        self.response.set_status(code)
        self.response.clear()
        
    def handle_exception(self, exception):
        """Called if this handler throws an exception during execution."""
        self.error(500)
        logging.error(exception)
        if DEBUG:
            self.response.clear()
            self.response.headers['Content-Type'] = 'text/plain'
            self.response.out.write(exception)

class WSGIApplication(object):
    """Web Application"""
    def __init__(self, mapping):
        self.mapping = []
        for regex, handler in mapping:
            self.mapping.append((re.compile(self.fix_regex(regex)), handler))

        # Authorization
        self.nonce = {}
        self.realm = product_name
        
    def fix_regex(self, regex):
        """ fix regular expressions """
        if not regex.startswith('^'):
            regex = '^' + regex
        if not regex.endswith('$'):
            regex += '$'
        return regex
    
    def bad_auth(self, response, start_response):
        """ Bad authorization """
        if utility.webconfig.Read("authorization") == "basic":
            response.headers['WWW-Authenticate'] = 'Basic realm=%s' % (self.realm)
            response.set_status(401)
        else:
            nonce = md5('%s:%s' % (time.time(), random.random())).hexdigest()
            opaque = md5('%s:%s' % (time.time(), random.random())).hexdigest()
            self.nonce[nonce] = None
            parts = {'realm':self.realm, 'qop':'auth', 'nonce':nonce, 'opaque':opaque}
            head = ', '.join(['%s="%s"' % (k, v) for (k, v) in parts.items()])
            response.headers['WWW-Authenticate'] = 'Digest %s' % head
            response.set_status(401)
        return response.wsgi_write(start_response)

    def bad_ip(self, response, start_response):
        """ Bad IP address """
        response.set_status(403)
        return response.wsgi_write(start_response)

    def auth(self, request):
        """ Check authorization """
        # Check if needed
        authorization = request.authorization
        conf_username = utility.webconfig.Read("username")
        conf_password = utility.webconfig.Read("password")
        if not conf_username:
            return True
        if authorization is None:
            return False

        if utility.webconfig.Read("authorization") == "basic":
            # Verify scheme
            authmeth, auth = authorization.split(' ', 1)
            if authmeth.lower() != 'basic':
                return False
            # Verify username and password
            auth = auth.strip().decode('base64')
            username, password = auth.split(':', 1)
            if username != conf_username or password != conf_password:
                return False
            return True
        else:
            # Verify scheme
            if not ' ' in authorization:
                return False
            authmeth, auth = authorization.split(' ', 1)
            if 'digest' != authmeth.lower():
                return False
            # Verify username and password
            amap = {}
            for itm in auth.split(','):
                k, v = [s.strip() for s in itm.split('=', 1)]
                amap[k] = v.replace('"', '')
            try:
                username = amap['username']
                authpath = amap['uri']
                nonce = amap['nonce']
                realm = amap['realm']
                response = amap['response']
                if not authpath.split('?', 1)[0] in request.fullpath:
                    return False
                if realm != self.realm:
                    return False
                qop = amap.get('qop', '')
                cnonce = amap.get('cnonce', '')
                nc = amap.get('nc', '00000000')
                if qop:
                    if 'auth' != qop:
                        return False
                    if not (nonce and nc):
                        return False
            except:
                return False
            ha1 = md5('%s:%s:%s' % (conf_username, self.realm, conf_password)).hexdigest()
            ha2 = md5('%s:%s' % (request.method, authpath)).hexdigest()
            if qop:
                chk = '%s:%s:%s:%s:%s:%s' % (ha1, nonce, nc, cnonce, qop, ha2)
            else:
                chk = '%s:%s:%s' % (ha1, nonce, ha2)
            if response != md5(chk).hexdigest():
                if nonce in self.nonce:
                    del self.nonce[nonce]
                return False
            pnc = self.nonce.get(nonce, '00000000')
            if nc <= pnc:
                if nonce in self.nonce:
                    del self.nonce[nonce]
                return False
            self.nonce[nonce] = nc
            return True
                    
    def __call__(self, environ, start_response):
        """Handle incoming request"""
        # Create request and response objects
        request = Request(environ)
        response = Response()

        # Check scheme
        if request.scheme != 'http':
            response.set_status(404)
            return response.wsgi_write(start_response)
        
        # Check ip
        allowed = utility.webconfig.Read("allowed", "bencode-list")
        if allowed and environ.get('REMOTE_ADDR') not in allowed:
            return self.bad_ip(response, start_response)
        
        # Check authorization
        if not self.auth(request):
            return self.bad_auth(response, start_response)

        # Find the right handler
        handler = None
        groups = ()
        for regex, handler_class in self.mapping:
            match = regex.match(request.path)
            if match:
                handler = handler_class()
                handler.initialize(request, response)
                groups = match.groups()
                break

        # Handle the request
        if handler:
            methods = {'GET': handler.get, 'POST': handler.post, 'HEAD': handler.head,
                       'OPTIONS': handler.options, 'TRACE': handler.trace}
            try:
                func = methods.get(request.method)
                if func:
                    func(*groups)
                else:
                    handler.error(501)
            except Exception, e:
                handler.handle_exception(format_exc())
        else:
            response.set_status(404)

        return response.wsgi_write(start_response)

class SimpleServer(simple_server.WSGIServer):
    def __init__(self, *args, **kwargs):
        simple_server.WSGIServer.__init__(self, *args, **kwargs)

    def DoStop(self):
        self.server_close()
        
    def DoStart(self):
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        self.OnBind()
        self.OnActivate()

    def ChangeAddress(self, addr):
        self.server_address = addr

    def OnBind(self):
        simple_server.WSGIServer.server_bind(self)
        
    def server_bind(self):
        pass

    def OnActivate(self):
        simple_server.WSGIServer.server_activate(self)
        
    def server_activate(self):
        pass
    
def app_get(mapping, host = '', port = 8080):
    return simple_server.make_server(host, port, WSGIApplication(mapping), SimpleServer)
    
if __name__ == "__main__":
    app = app_get([('/', RequestHandler), ('/test', RequestHandler)])
    app.serve_forever()
